﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ZackFlame.IronBrainFuck
{
    /// <summary>
    /// Машина BrainFuck, осуществляющая интерпретацию программы.
    /// </summary>
    public sealed class BrainFuckInterpreter : BrainFuckMachine
    {
        /// <summary>
        /// Интерпретирует BrainFuck программу на данной машине.
        /// </summary>
        /// <param name="brainFuckProgram">Код программы для интерпретации.</param>
        public override void Execute(string brainFuckProgram)
        {
            int brackets = 0;
            for (int i = 0; i < brainFuckProgram.Length; i++)
            {
                char ch = brainFuckProgram[i];
                if (ch == '[')
                    brackets++;
                else if (ch == ']')
                    brackets--;
            }

            if (brackets > 0)
                throw new InvalidProgramException("Closing bracket expected");
            if (brackets < 0)
                throw new InvalidProgramException("Opening bracket expected");

            int programIndex = 0;
            Run(brainFuckProgram, ref programIndex);
        }

        private void Run(string program, ref int programIndex)
        {
            while (programIndex < program.Length)
            {
                switch (program[programIndex])
                {
                    case '+':
                        Cells[CellIndex]++;
                        break;

                    case '-':
                        Cells[CellIndex]--;
                        break;

                    case '>':
                        CellIndex++;
                        break;

                    case '<':
                        CellIndex--;
                        break;

                    case '.':
                        IOHelper.WriteChar(Cells[CellIndex], Output);
                        break;

                    case ',':
                        Cells[CellIndex] = IOHelper.ReadChar(Input);
                        break;

                    case '#':
                        IOHelper.WriteCell(CellIndex, Cells[CellIndex], Output);
                        break;

                    case '@':
                        Cells[CellIndex] = IOHelper.ReadCell(Input);
                        break;

                    case '[':
                        Cycle(program, ref programIndex);
                        break;

                    case ']':
                        return;
                }

                programIndex++;
            }
        }

        private void Cycle(string program, ref int programIndex)
        {
            if (Cells[CellIndex] != 0)
            {
                int cycleStart = programIndex + 1;

                do
                {
                    programIndex = cycleStart;
                    Run(program, ref programIndex);
                }
                while (Cells[CellIndex] != 0);
            }
            else
            {
                int bracketCount = 1;
                while (bracketCount > 0)
                {
                    programIndex++;

                    if (program[programIndex] == '[')
                        bracketCount++;
                    else if (program[programIndex] == ']')
                        bracketCount--;
                }
            }
        }
    }
}
